package de.luh.sim.ftn.panel;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
import javax.swing.JPanel;

import de.luh.sim.ftn.graph.Edge;
import de.luh.sim.ftn.graph.Node;

/**
 * JPanel for drawing Graphs Testing Optimization Algorithm for Graphs
 * 
 * @author Dominik Molitor
 * @version 0.3
 * 
 */
public class DrawPanel extends JPanel {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	private ArrayList<Node> displayNodes;
	private ArrayList<Node> conNodes;

	private ArrayList<Edge> displayEdges;
	private ArrayList<Edge> allEdges;
	private ArrayList<Edge> mstEdges;

	private double maxCosts;
	private double totalLength;
	private double costMST;
	private int pointStr = 10;
	private int numberOfPoints;

	private double wantedCost = 2.0;
	private double wantedFt = 0.8;
	private double costToFtRatio = 0.5;
	private double currentEfficiency;

	public DrawPanel() {
		displayNodes = new ArrayList<Node>();
		displayEdges = new ArrayList<Edge>();
		allEdges = new ArrayList<Edge>();
		maxCosts = Double.MAX_VALUE;
		numberOfPoints = 10;
		createPoints(numberOfPoints);
		setBackground(Color.WHITE);
	}

	@Override
	public void paintComponent(Graphics g) {
		super.paintComponent(g);
		doDrawing(g);
	}

	private void doDrawing(Graphics g) {
		Graphics2D g2d = (Graphics2D) g;

		if (displayEdges != null) {
			g2d.setColor(Color.BLUE);
			g2d.setStroke(new BasicStroke(2));
			for (Edge e : displayEdges) {
				// TODO Maybe Color last edited/checked in different Color for
				// better Visualization
				g2d.draw(new Line2D.Double(e.getN1(), e.getN2()));
			}

		}
		if (displayNodes != null) {
			g2d.setColor(Color.RED);
			for (Point2D p : displayNodes) {
				g2d.fillOval((int) p.getX() - pointStr / 2, (int) p.getY() - pointStr / 2, pointStr, pointStr);

			}
		}
	}

	/**
	 * Creates new random points
	 * 
	 * @param number
	 *            Number of points
	 */
	public void createPoints(int number) {
		int id = 1;
		displayNodes = new ArrayList<Node>();
		displayEdges = new ArrayList<Edge>();
		allEdges = new ArrayList<Edge>();
		mstEdges = new ArrayList<Edge>();

		for (int i = 0; i < number; i++) {
			// Gives Numbers between 1-449 for panelsize 500
			// We want to have Points between 25-475 so we add 26
			int x = (int) (Math.random() * 450 + 26);
			int y = (int) (Math.random() * 450 + 26);
			displayNodes.add(new Node(x, y, id));
			id++;

		}
		// System.out.println("New Nodes generated: " + displayNodes.size());
		createAllEdges();
		generateMSTPrimsAlgo();
		costMST = 0;
		for (Edge e : mstEdges) {
			costMST += e.getCost();
		}

		// System.out.println("MaxCostMST: " + costMST);
	}

	/**
	 * Generates AllEdges
	 * 
	 */
	public void createAllEdges() {
		int id = 1;
		for (Node n : displayNodes) {
			ArrayList<Node> tmpNodes = new ArrayList<Node>();
			for (Node n1 : displayNodes) {
				tmpNodes.add(n1.copy());
			}
			tmpNodes.remove(n);
			for (Node n2 : tmpNodes) {
				Edge tmpEdge = new Edge(n, n2, id);
				id++;
				if (!allEdges.contains(tmpEdge)) {
					allEdges.add(tmpEdge);
				}
			}
		}
	}

	/**
	 * Generates Minimal Spanning Tree with Prim's Algorithm
	 * http://en.wikipedia.org/wiki/Prim%27s_algorithm
	 * 
	 */
	public void generateMSTPrimsAlgo() {
		maxCosts = Double.MAX_VALUE;
		int id = 1;
		ArrayList<Edge> edges = new ArrayList<Edge>();
		conNodes = new ArrayList<Node>();
		Edge tmpEdge = new Edge();
		for (Edge e : allEdges) {
			if (e.getCost() < maxCosts) {
				maxCosts = e.getCost();
				tmpEdge = new Edge(e.getN1(), e.getN2(), id);
			}
		}
		conNodes.add(tmpEdge.getN1());
		conNodes.add(tmpEdge.getN2());

		tmpEdge = new Edge(tmpEdge.getN1(), tmpEdge.getN2(), id);
		id++;
		edges.add(tmpEdge);
		while (!(conNodes.size() == displayNodes.size())) {
			maxCosts = Double.MAX_VALUE;
			for (Edge e : allEdges) {
				if ((conNodes.contains(e.getN1()) && !conNodes.contains(e.getN2()))
						|| (!conNodes.contains(e.getN1()) && conNodes.contains(e.getN2()))) {
					if (e.getCost() < maxCosts) {
						maxCosts = e.getCost();
						tmpEdge = e;
					}
				}

			}
			edges.add(new Edge(tmpEdge.getN1(), tmpEdge.getN2(), id));
			id++;
			if (conNodes.contains(tmpEdge.getN1())) {
				conNodes.add(tmpEdge.getN2());
			} else {
				conNodes.add(tmpEdge.getN1());

			}
		}
		mstEdges = edges;
	}

	/**
	 * Sets mstEdges to displayEdges
	 * 
	 */
	public void setMSTPrimsAlgo() {
		// Start with MST
		displayEdges.clear();
		for (Edge e : mstEdges) {
			displayEdges.add(e.copy());
		}

		addEdgesToNodes();
		totalLength = totalLength();

	}

	/**
	 * Sets allEdges to displayEdges
	 * 
	 */
	public void setAllEdges() {
		displayEdges.clear();
		for (Edge e : allEdges) {
			displayEdges.add(e.copy());
		}

		addEdgesToNodes();
		totalLength = totalLength();

	}

	/**
	 * Starts and Displays Hill Climbing Algorithm
	 * 
	 * 
	 */
	public void hillClimbing(int ms) {
		// Start with MST
		displayEdges.clear();
		for (Edge e : mstEdges) {
			displayEdges.add(e.copy());
		}

		addEdgesToNodes();
		int maxEdges = allEdges.size();
		System.out.println("maxEdges: " + maxEdges);

		// Set currentEfficiency
		currentEfficiency = efficiencyCheck();

		// Start with Random Edge
		Random r = new Random();
		int index = r.nextInt(maxEdges);
		int counter = 0;
		boolean removed = false;

		// Do while ( not All Edges have been checked)
		while (!(counter == (maxEdges + 1))) {

			// Threadsleep and repaint so u can actually see what is happening
			paintComponent(this.getGraphics());
			try {
				Thread.sleep(ms);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("Counter: " + counter);
			System.out.println("Index: " + index);

			// Check selected Edge
			if (displayEdges.contains(allEdges.get(index))) {

				// Remove if exited in Current Graph
				displayEdges.remove(allEdges.get(index));
				removed = true;
				System.out.println("Found Edges and removed!");
				addEdgesToNodes();
			} else {

				// Add if not exited
				displayEdges.add(allEdges.get(index));
				removed = false;
				System.out.println("Didnt find so added!");
				addEdgesToNodes();
			}

			// Check efficiency again
			if (efficiencyCheck() > currentEfficiency) {

				// If better efficiency: Save changes, update efficiency and
				// start with new random edge
				System.out.println("CurrentEfficiency was: " + currentEfficiency);
				currentEfficiency = efficiencyCheck();
				System.out.println("New Efficiency is: " + currentEfficiency);
				index = r.nextInt(maxEdges);
				counter = 0;
			} else {

				// if worse efficiency: revert changes, try next index in list
				if (removed) {
					displayEdges.add(allEdges.get(index));
					addEdgesToNodes();
				} else {
					displayEdges.remove(allEdges.get(index));
					addEdgesToNodes();
				}
				System.out.println("Efficiency worse.. Try next Index!");
				counter++;
				if (index == maxEdges - 1) {
					index = 0;
				} else {
					index++;
				}

			}

		}
		totalLength = totalLength();
		System.out.println("DONE!!!!!!!");
		System.out.println();
		System.out.println();
		System.out.println();
	}

	/**
	 * Returns total length of displayed edges.
	 * 
	 * @return total length of displayed Edges
	 */
	private double totalLength() {
		double totalLength = 0;
		for (Edge e : displayEdges) {
			totalLength += e.getCost();
		}
		// System.out.println("Number of Edges: " + displyEdges.size());
		// System.out.println(totalLength);
		return totalLength;
	}

	/**
	 * Updates current edges in displayed nodes important for
	 * Fault-Tolerance-Calculation.
	 * 
	 */
	public void addEdgesToNodes() {
		for (Node n : displayNodes) {
			n.clear();
		}

		for (Node n : displayNodes) {
			for (Edge e : displayEdges) {
				if ((n.equals(e.getN1()) || n.equals(e.getN2())) && !n.containsEdge(e)) {
					n.addEdge(e);
				}
			}
		}
	}

	/**
	 * 
	 * @return returns current Efficiency
	 */
	private double efficiencyCheck() {
		double currentFt = faultTolerance();
		double currentCost = cost();

		System.out.println("Current // Fault Tolerance : Cost");
		System.out.println("     " + currentFt + " : " + currentCost);

		// TODO Implement weighted efficiency
		// Weighted not yet Tested
		// currentFt = currentFt * ((1.0 - costToFtRatio) * 100);
		// currentCost = currentCost * (costToFtRatio * 100);

		// System.out.println("After Weigthed calculation:");
		// System.out.println("Ft    :    Cost");
		// System.out.println(currentFt + " : " + currentCost);

		System.out.println(currentFt / currentCost);

		return currentFt / currentCost;
	}

	/**
	 * Calculates current fault tolerance
	 * 
	 * @return current fault tolerance as double
	 */
	public double faultTolerance() {
		double sumWeight = 0;
		for (Edge e : displayEdges) {
			ArrayList<Node> tmpNodes = new ArrayList<Node>();
			for (Node n : displayNodes) {
				tmpNodes.add(n.copy());
			}
			for (Node node : tmpNodes) {
				if (node.containsEdge(e)) {
					node.removeEdge(e);
				}
			}
			if (BFS(e.getN1(), tmpNodes)) {
				sumWeight += e.getCost();
			}
		}

		return sumWeight / totalLength();

	}

	/**
	 * Checks if all
	 * 
	 * @param testNode
	 * @param nodes
	 * @return
	 */
	private boolean BFS(Node testNode, ArrayList<Node> nodes) {
		for (Node n : nodes) {
			n.setVisted(0);
		}
		nodes.get(nodes.indexOf(testNode)).setVisted(1);
		// System.out.println("Start with Node and add to Queue:" +
		// nodes.get(nodes.indexOf(testNode)));
		Queue<Node> queue = new LinkedList<Node>();
		queue.add(nodes.get(nodes.indexOf(testNode)));
		while (!queue.isEmpty()) {
			Node n = queue.remove();
			// System.out.println("Von Queue removed:" + n.getId());
			for (Node n2 : nodes.get(nodes.indexOf(n)).getNeighbours()) {
				if (nodes.get(nodes.indexOf(n2)).isVisted() == 0) {
					nodes.get(nodes.indexOf(n2)).setVisted(1);
					queue.add(nodes.get(nodes.indexOf(n2)));
					// System.out.println("Neighbours gefunden + adde to queue"
					// + nodes.get(nodes.indexOf(n2)));
				}
			}
			nodes.get(nodes.indexOf(n)).setVisted(2);

		}
		// System.out.println(allVisited(nodes));
		return allVisited(nodes);

	}

	private boolean allVisited(ArrayList<Node> nodes) {
		for (Node n : nodes) {
			// System.out.println("Node: " + n.getId() + " = visted: " +
			// n.isVisted());
			if (n.isVisted() != 2) {
				return false;
			}
		}
		return true;
	}

	public int numberOfEdges() {
		return displayEdges.size();
	}

	public double cost() {
		// System.out.println("Cost= " + totalLength + " /" + costMST + " =" +
		// totalLength / costMST);
		return totalLength() / costMST;
	}

	public double averageDistance() {
		return totalLength / (double) displayEdges.size();
	}

	public ArrayList<Node> getNodes() {
		return displayNodes;
	}

	public void setNodes(ArrayList<Node> nodes) {
		this.displayNodes = nodes;
	}

	public ArrayList<Edge> getEdges() {
		return displayEdges;
	}

	public void setEdges(ArrayList<Edge> edges) {
		this.displayEdges = edges;
	}

	public double getMaxCostMST() {
		return costMST;
	}

	public double getTotalLength() {
		return totalLength;
	}

	public double getWantedCost() {
		return wantedCost;
	}

	public void setWantedCost(double wantedCost) {
		this.wantedCost = wantedCost;
	}

	public double getWantedFt() {
		return wantedFt;
	}

	public void setWantedFt(double wantedFt) {
		this.wantedFt = wantedFt;
	}

	public double getCostToFtRatio() {
		return costToFtRatio;
	}

	public void setCostToFtRatio(double costToFtRatio) {
		this.costToFtRatio = costToFtRatio;
	}

	public double getCurrentEfficieny() {
		return currentEfficiency;
	}

}
